﻿<%@ Page Title="" Language="VB" MasterPageFile="~/Master/Site.master" %>

<%@ Import Namespace="System.IO" %>

<%@ Import Namespace="System.Web.Configuration" %>

<script runat="server">
    Private Const VirtualImageRoot As String = "~/"
    Private selectedFolderName As String

    Private Sub Page_Init()
        UserRoles.DataSource = Roles.GetAllRoles()
        UserRoles.DataBind()

        UserList.DataSource = Membership.GetAllUsers()
        UserList.DataBind()

        If IsPostBack Then
            selectedFolderName = ""
        Else
            selectedFolderName = Request.QueryString("selectedFolderName")
        End If
    End Sub

    Private Sub Page_Load()
        If Not IsPostBack Then
            PopulateTree()
        End If
    End Sub

    Private Sub Page_PreRender()
        '
        '		 * The call to DisplayAccessRules, and the subsequent binding of the gridview, MUST occur
        '		 * inside the Page_PreRender event. I don't fully understand why, but this fixes two more .NET gotchas:
        '		 * 1) My technique to hide the delete rule button did not work the other way.
        '		 *    Why? Upon page refresh, ALL ROWS HAD BUTTONS because I'd been calling DisplayAccessRules
        '		 *    from the FolderTree_SelectedNodeChanged event.
        '		 * 2) I first moved this into the Page_Load event, but that caused the weird Event validation error
        '		 *    that occurs when ASP.NET thinks someone is hacking your postback values.
        '		 * 
        '		 * This appears to be working now, so I'm leaving well enough alone.
        '		 

        If FolderTree.SelectedNode IsNot Nothing Then
            DisplayAccessRules(FolderTree.SelectedValue)
            SecurityInfoSection.Visible = True
        End If
    End Sub

    Private Sub PopulateTree()
        '
        '		 * The PopulateTree and AddNodeAndDescendents are taken almost verbatim from Scott Mitchell's article
        '		 * "Using the TreeView Control and a DataList to Create an Online Image Gallery", which is located at 
        '		 * http://aspnet.4guysfromrolla.com/articles/083006-1.aspx
        '		 


        ' Populate the tree based on the subfolders of the specified VirtualImageRoot
        Dim rootFolder As New DirectoryInfo(Server.MapPath(VirtualImageRoot))
        Dim root As TreeNode = AddNodeAndDescendents(rootFolder, Nothing)
        FolderTree.Nodes.Add(root)
        Try
            FolderTree.SelectedNode.ImageUrl = "~/i/target.gif"
        Catch
        End Try
    End Sub

    Private Function AddNodeAndDescendents(folder As DirectoryInfo, parentNode As TreeNode) As TreeNode
        '
        '		 * The PopulateTree and AddNodeAndDescendents are taken almost verbatim from Scott Mitchell's article
        '		 * "Using the TreeView Control and a DataList to Create an Online Image Gallery", which is located at 
        '		 * http://aspnet.4guysfromrolla.com/articles/083006-1.aspx
        '		 


        ' Add the TreeNode, displaying the folder's name and storing the full path to the folder as the value...

        Dim virtualFolderPath As String
        If parentNode Is Nothing Then
            virtualFolderPath = VirtualImageRoot
        Else
            virtualFolderPath = Convert.ToString(parentNode.Value + folder.Name) & "/"
        End If

        Dim node As New TreeNode(folder.Name, virtualFolderPath)
        node.Selected = (folder.Name = selectedFolderName)

        ' Recurse through this folder's subfolders
        Dim subFolders As DirectoryInfo() = folder.GetDirectories()
        For Each subFolder As DirectoryInfo In subFolders
            If subFolder.Name <> "_controls" AndAlso subFolder.Name <> "App_Data" Then
                Dim child As TreeNode = AddNodeAndDescendents(subFolder, node)
                node.ChildNodes.Add(child)
            End If
        Next
        Return node
        ' Return the new TreeNode
    End Function

    Protected Sub FolderTree_SelectedNodeChanged(sender As Object, e As EventArgs)
        '
        '		 * I want to reset the Add Rule form field values whenever the user moves folders.
        '		 * Note that the FALSE statements below are all necessary. It is not sufficient to set
        '		 * the desired radio to TRUE, since the ASP.NET framework seems to treat radio buttons
        '		 * as individual items even if they share the same group name.
        '		 

        ActionDeny.Checked = True
        ActionAllow.Checked = False
        ApplyRole.Checked = True
        ApplyUser.Checked = False
        ApplyAllUsers.Checked = False
        ApplyAnonUser.Checked = False
        UserRoles.SelectedIndex = 0
        UserList.SelectedIndex = 0

        RuleCreationError.Visible = False

        ResetFolderImageUrls(FolderTree.Nodes(0))
        ' Restore previously selected folder's ImageUrl.
        FolderTree.SelectedNode.ImageUrl = "~/i/target.gif"
        ' Set the newly selected folder's ImageUrl.
    End Sub

    Private Sub ResetFolderImageUrls(parentNode As TreeNode)
        '
        '		 


        parentNode.ImageUrl = "~/i/folder.gif"

        ' Recurse through this node's child nodes.
        Dim nodes As TreeNodeCollection = parentNode.ChildNodes
        For Each childNode As TreeNode In nodes
            ResetFolderImageUrls(childNode)
        Next
    End Sub

    Private Sub DisplayAccessRules(virtualFolderPath As String)
        If Not virtualFolderPath.StartsWith(VirtualImageRoot) OrElse virtualFolderPath.IndexOf("..") >= 0 Then

            Throw New ApplicationException("An attempt to access a folder outside of the website directory has been detected and blocked.")
        End If
        Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
        Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
        Dim authorizationRules As AuthorizationRuleCollection = systemWeb.Authorization.Rules
        RulesGrid.DataSource = authorizationRules
        RulesGrid.DataBind()

        TitleOne.InnerText = "Rules applied to " & virtualFolderPath
        TitleTwo.InnerText = "Create new rule for " & virtualFolderPath
    End Sub

    Private Sub RowDataBound(sender As Object, e As GridViewRowEventArgs)
        If e.Row.RowType = DataControlRowType.DataRow Then
            Dim rule As AuthorizationRule = DirectCast(e.Row.DataItem, AuthorizationRule)
            If Not rule.ElementInformation.IsPresent Then
                e.Row.Cells(3).Text = "Inherited from higher level"
                e.Row.Cells(4).Text = "Inherited from higher level"
                e.Row.CssClass = "odd"
            End If
        End If
    End Sub

    Private Function GetAction(rule As AuthorizationRule) As String
        Return rule.Action.ToString()
    End Function
    Private Function GetRole(rule As AuthorizationRule) As String
        Return rule.Roles.ToString()
    End Function
    Private Function GetUser(rule As AuthorizationRule) As String
        Return rule.Users.ToString()
    End Function
    Private Sub DeleteRule(sender As Object, e As EventArgs)
        '
        '		 * This is working quite well, however there is a defect that I am not planning to fix right now.
        '		 * If you delete a rule, then attempt to delete another rule from the same folder without
        '		 * refreshing the page, you'll get a page error. The workaround is to re-click the folder in the
        '		 * tree to refresh it, then delete the rule.
        '		 * Don't feel like worrying about this right now.
        '		 * 
        '		 * Note: this problem may have been fixed already.
        '		 * I stopped using the session array method for handling things.
        '		 * This may have fixed it. I'll test later.
        '		 

        Dim button As Button = DirectCast(sender, Button)
        Dim item As GridViewRow = DirectCast(button.Parent.Parent, GridViewRow)
        Dim virtualFolderPath As String = FolderTree.SelectedValue
        Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
        Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
        Dim section As AuthorizationSection = DirectCast(systemWeb.Sections("authorization"), AuthorizationSection)
        section.Rules.RemoveAt(item.RowIndex)
        config.Save()
    End Sub
    Private Sub MoveUp(sender As Object, e As EventArgs)
        MoveRule(sender, e, "up")
    End Sub
    Private Sub MoveDown(sender As Object, e As EventArgs)
        MoveRule(sender, e, "down")
    End Sub

    Private Sub MoveRule(sender As Object, e As EventArgs, upOrDown As String)

        upOrDown = upOrDown.ToLower()

        If upOrDown = "up" OrElse upOrDown = "down" Then
            Dim button As Button = DirectCast(sender, Button)
            Dim item As GridViewRow = DirectCast(button.Parent.Parent, GridViewRow)
            Dim selectedIndex As Integer = item.RowIndex
            If (selectedIndex > 0 AndAlso upOrDown = "up") OrElse (upOrDown = "down") Then
                Dim virtualFolderPath As String = FolderTree.SelectedValue
                Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
                Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
                Dim section As AuthorizationSection = DirectCast(systemWeb.Sections("authorization"), AuthorizationSection)

                ' Pull the local rules out of the authorization section, deleting them from same:
                Dim rulesArray As ArrayList = PullLocalRulesOutOfAuthorizationSection(section)
                If upOrDown = "up" Then
                    LoadRulesInNewOrder(section, rulesArray, selectedIndex, upOrDown)
                ElseIf upOrDown = "down" Then
                    If selectedIndex < rulesArray.Count - 1 Then
                        LoadRulesInNewOrder(section, rulesArray, selectedIndex, upOrDown)
                    Else
                        ' DOWN button in last row was pressed. Load the rules array back in without resorting.
                        For x As Integer = 0 To rulesArray.Count - 1
                            section.Rules.Add(DirectCast(rulesArray(x), AuthorizationRule))
                        Next
                    End If
                End If
                config.Save()
            End If
        End If
    End Sub
    Private Sub LoadRulesInNewOrder(section As AuthorizationSection, rulesArray As ArrayList, selectedIndex As Integer, upOrDown As String)
        '
        '		 * I hope this is simple enough.
        '		 * Imagine you have five local rules and you click a button to move the middle one.
        '		 * In that scenario, all three of these methods will add rules.
        '		 * If, however, there are only two local rules to start with, then only the middle method will add rules.
        '		 * The first and third methods won't do anything, because their FOR loops will never execute.
        '		 


        AddFirstGroupOfRules(section, rulesArray, selectedIndex, upOrDown)
        AddTheTwoSwappedRules(section, rulesArray, selectedIndex, upOrDown)
        AddFinalGroupOfRules(section, rulesArray, selectedIndex, upOrDown)
    End Sub
    Private Sub AddFirstGroupOfRules(section As AuthorizationSection, rulesArray As ArrayList, selectedIndex As Integer, upOrDown As String)
        Dim adj As Integer
        If upOrDown = "up" Then
            adj = 1
        Else
            adj = 0
        End If
        For x As Integer = 0 To selectedIndex - adj - 1
            section.Rules.Add(DirectCast(rulesArray(x), AuthorizationRule))
        Next
    End Sub
    Private Sub AddTheTwoSwappedRules(section As AuthorizationSection, rulesArray As ArrayList, selectedIndex As Integer, upOrDown As String)
        If upOrDown = "up" Then
            section.Rules.Add(DirectCast(rulesArray(selectedIndex), AuthorizationRule))
            section.Rules.Add(DirectCast(rulesArray(selectedIndex - 1), AuthorizationRule))
        ElseIf upOrDown = "down" Then
            section.Rules.Add(DirectCast(rulesArray(selectedIndex + 1), AuthorizationRule))
            section.Rules.Add(DirectCast(rulesArray(selectedIndex), AuthorizationRule))
        End If
    End Sub
    Private Sub AddFinalGroupOfRules(section As AuthorizationSection, rulesArray As ArrayList, selectedIndex As Integer, upOrDown As String)
        Dim adj As Integer
        If upOrDown = "up" Then
            adj = 1
        Else
            adj = 2
        End If
        For x As Integer = selectedIndex + adj To rulesArray.Count - 1
            section.Rules.Add(DirectCast(rulesArray(x), AuthorizationRule))
        Next
    End Sub
    Private Function PullLocalRulesOutOfAuthorizationSection(section As AuthorizationSection) As ArrayList
        ' First load the local rules into an ArrayList.

        Dim rulesArray As New ArrayList()
        For Each rule As AuthorizationRule In section.Rules
            If rule.ElementInformation.IsPresent Then
                rulesArray.Add(rule)
            End If
        Next

        ' Next delete the rules from the section.
        For Each rule As AuthorizationRule In rulesArray
            section.Rules.Remove(rule)
        Next
        Return rulesArray
    End Function

    Private Sub CreateRule(sender As Object, e As EventArgs)
        Dim newRule As AuthorizationRule
        If ActionAllow.Checked Then
            newRule = New AuthorizationRule(AuthorizationRuleAction.Allow)
        Else
            newRule = New AuthorizationRule(AuthorizationRuleAction.Deny)
        End If

        If ApplyRole.Checked AndAlso UserRoles.SelectedIndex > 0 Then
            newRule.Roles.Add(UserRoles.Text)
            AddRule(newRule)
        ElseIf ApplyUser.Checked AndAlso UserList.SelectedIndex > 0 Then
            newRule.Users.Add(UserList.Text)
            AddRule(newRule)
        ElseIf ApplyAllUsers.Checked Then
            newRule.Users.Add("*")
            AddRule(newRule)
        ElseIf ApplyAnonUser.Checked Then
            newRule.Users.Add("?")
            AddRule(newRule)
        End If
    End Sub

    Private Sub AddRule(newRule As AuthorizationRule)
        Dim virtualFolderPath As String = FolderTree.SelectedValue
        Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
        Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
        Dim section As AuthorizationSection = DirectCast(systemWeb.Sections("authorization"), AuthorizationSection)
        section.Rules.Add(newRule)
        Try
            config.Save()
            RuleCreationError.Visible = False
        Catch ex As Exception
            RuleCreationError.Visible = True
            RuleCreationError.Text = "<div class=""alert""><br />An error occurred and the rule was not added. I saw this happen during testing when I attempted to create a rule that the ASP.NET infrastructure realized was redundant. Specifically, I had the rule <i>DENY ALL USERS</i> in one folder, then attempted to add the same rule in a subfolder, which caused ASP.NET to throw an exception.<br /><br />Here's the error message that was thrown just now:<br /><br /><i>" + ex.Message & "</i></div>"
        End Try
    End Sub
</script>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
    <!-- #include file="_nav.aspx -->

<table class="webparts">
<tr>
	<th>Website Access Rules</th>
</tr>
<tr>
	<td class="details" valign="top">
		<p>
		Use this page to manage access rules for your Web site. Rules are applied to folders, thus providing robust folder-level security enforced by the ASP.NET infrastructure. Rules are persisted as XML in each folder's Web.config file. <i>Page-level security and inner-page security are not handled using this tool &mdash; they are handled using specialized code that is available to the Web Developers.</i>
		</p>



		<table>
		<tr>
			<td valign="top" style="padding-right: 30px;">
				<div class="treeview">
				<asp:TreeView runat="server" ID="FolderTree"
					OnSelectedNodeChanged="FolderTree_SelectedNodeChanged">
					<RootNodeStyle ImageUrl="~/i/folder.gif" />
					<ParentNodeStyle ImageUrl="~/i/folder.gif" />
					<LeafNodeStyle ImageUrl="~/i/folder.gif" />
					<SelectedNodeStyle Font-Underline="true" ForeColor="#A21818" />
				</asp:TreeView>
				</div> 
			</td>

			<td valign="top" style="padding-left: 30px; border-left: 1px solid #999;">
			<asp:Panel runat="server" ID="SecurityInfoSection" Visible="false">
				<h2 runat="server" id="TitleOne" class="alert"></h2>
				
				<p>
				Rules are applied in order. The first rule that matches applies, and the permission in each rule overrides the permissions in all following rules. Use the Move Up and Move Down buttons to change the order of the selected rule. Rules that appear dimmed are inherited from the parent and cannot be changed at this level. 
				</p>
				
				<asp:GridView runat="server" ID="RulesGrid" AutoGenerateColumns="false"
				CssClass="list" GridLines="none"
				OnRowDataBound="RowDataBound"
				>
					<Columns>
						<asp:TemplateField HeaderText="Action">
							<ItemTemplate>
								<%# GetAction(DirectCast(Container.DataItem, AuthorizationRule))%>
							</ItemTemplate>
						</asp:TemplateField>
						<asp:TemplateField HeaderText="Roles">
							<ItemTemplate>
								<%# GetRole(DirectCast(Container.DataItem, AuthorizationRule))%>
							</ItemTemplate>
						</asp:TemplateField>
						<asp:TemplateField HeaderText="User">
							<ItemTemplate>
								<%# GetUser(DirectCast(Container.DataItem, AuthorizationRule))%>
							</ItemTemplate>
						</asp:TemplateField>
						<asp:TemplateField HeaderText="Delete Rule">
							<ItemTemplate>
								<asp:Button ID="Button1" runat="server" Text="Delete Rule" CommandArgument="<%# DirectCast(Container.DataItem, AuthorizationRule) %>" OnClick="DeleteRule" OnClientClick="return confirm('Click OK to delete this rule.')" />
							</ItemTemplate>
						</asp:TemplateField>
						<asp:TemplateField HeaderText="Move Rule">
							<ItemTemplate>
								<asp:Button ID="Button2" runat="server" Text="  Up  " CommandArgument="<%# DirectCast(Container.DataItem, AuthorizationRule) %>" OnClick="MoveUp" />
								<asp:Button ID="Button3" runat="server" Text="Down" CommandArgument="<%# DirectCast(Container.DataItem, AuthorizationRule) %>" OnClick="MoveDown" />
							</ItemTemplate>
						</asp:TemplateField>
					</Columns>
				</asp:GridView>

				<br />
				<hr />
				<h2 runat="server" id="TitleTwo" class="alert"></h2>
				<b>Action:</b>
				<asp:RadioButton runat="server" ID="ActionDeny" GroupName="action" 
					Text="Deny" Checked="true" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
				<asp:RadioButton runat="server" ID="ActionAllow" GroupName="action" 
					Text="Allow" />
				
				<br /><br />
				<b>Rule applies to:</b>
				<br />
				<asp:RadioButton runat="server" ID="ApplyRole" GroupName="applyto"
					Text="This Role:" Checked="true" />
				<asp:DropDownList ID="UserRoles" runat="server" AppendDataBoundItems="true">
				<asp:ListItem>Select Role</asp:ListItem>
				</asp:DropDownList>
				<br />
					
				<asp:RadioButton runat="server" ID="ApplyUser" GroupName="applyto"
					Text="This User:" />
				<asp:DropDownList ID="UserList" runat="server" AppendDataBoundItems="true">
				<asp:ListItem>Select User</asp:ListItem>
				</asp:DropDownList>	
				<br />
				
				
				<asp:RadioButton runat="server" ID="ApplyAllUsers" GroupName="applyto"
					Text="All Users (*)"  />
				<br />
				
				
				<asp:RadioButton runat="server" ID="ApplyAnonUser" GroupName="applyto"
					Text="Anonymous Users (?)"  />
				<br /><br />
				
				<asp:Button ID="Button4" runat="server" Text="Create Rule" OnClick="CreateRule"
					OnClientClick="return confirm('Click OK to create this rule.');" />
					
				<asp:Literal runat="server" ID="RuleCreationError"></asp:Literal>
			</asp:Panel>
			</td>
		</tr>
		</table>
	</td>
</tr>
</table>



</asp:Content>

